#include "ega.h"
#include "../lib/rtl.h"

RECT EgaScreenRect = {
    .left = 0,
    .top = 0,
    .right = 320,
    .bottom = 200
};

BOOL EgaInRect(LONG x, LONG y, LPRECT lpRect){
    if(x < lpRect->left || x >= lpRect->right)
        return FALSE;
    if(y < lpRect->top || y >= lpRect->bottom)
        return FALSE;
    return TRUE;
}

STATIC INLINE VOID EgaLimitRect(CONST LPRECT lpRect, DWORD dwWidth, DWORD dwHeight){
    if(lpRect->top > lpRect->bottom)
        __xchg(lpRect->top, lpRect->bottom);
    if(lpRect->left > lpRect->right)
        __xchg(lpRect->left, lpRect->right);
    lpRect->left = Range(lpRect->left, 0, dwWidth);
    lpRect->right = Range(lpRect->right, 0, dwWidth);
    lpRect->top = Range(lpRect->top, 0, dwHeight);
    lpRect->bottom = Range(lpRect->bottom, 0, dwHeight);
}

INLINE VOID EgaDrawPoint(CONST PVECTOR2I pvPoint, BYTE bColor){
    if(!EgaInRect(pvPoint->x, pvPoint->y, &EgaScreenRect))
        return;
    *((LPBYTE)0xC00A0000 + pvPoint->x + pvPoint->y * 0x140) = bColor;
}

STATIC INLINE LONG EgaGetBitmapRowSize(LONG biWidth, WORD biBitCount){
    REG LONG biBits;
    biBits = biWidth * (LONG)biBitCount;
    return (biBits & 0x1F ? (biBits & ~0x1F) + 0x20 : biBits) >> 3;
}

VOID EgaDrawTriangle(CONST PVECTOR2I pvPoints, BYTE bColor){
    if(pvPoints[0].y > pvPoints[1].y)
        Vec2ISwap(pvPoints, pvPoints + 1);
    if(pvPoints[1].y > pvPoints[2].y)
        Vec2ISwap(pvPoints + 1, pvPoints + 2);
    if(pvPoints[0].y > pvPoints[1].y)
        Vec2ISwap(pvPoints, pvPoints + 1);
    for(REG INT i = pvPoints[0].y; i < pvPoints[1].y; i++){
        INT start, end;
        start = (i - pvPoints[0].y) * (pvPoints[1].x - pvPoints[0].x) / (pvPoints[1].y - pvPoints[0].y) + pvPoints[0].x;
        end = (i - pvPoints[0].y) * (pvPoints[2].x - pvPoints[0].x) / (pvPoints[2].y - pvPoints[0].y) + pvPoints[0].x;
        if(start > end)
            __xchg(start, end);
        start = Range(start, 0, 320);
        end = Range(end, 0, 320);
        RtlFillMemory(((LPBYTE)0xC00A0000 + start + i * 0x140), bColor, end - start);
    }
    for(REG INT i = pvPoints[1].y; i < pvPoints[2].y; i++){
        INT start, end;
        start = (i - pvPoints[2].y) * (pvPoints[1].x - pvPoints[2].x) / (pvPoints[1].y - pvPoints[2].y) + pvPoints[2].x;
        end = (i - pvPoints[2].y) * (pvPoints[0].x - pvPoints[2].x) / (pvPoints[0].y - pvPoints[2].y) + pvPoints[2].x;
        if(start > end)
            __xchg(start, end);
        start = Range(start, 0, 320);
        end = Range(end, 0, 320);
        RtlFillMemory(((LPBYTE)0xC00A0000 + start + i * 0x140), bColor, end - start);
    }
    EgaDrawPoint(pvPoints + 2, bColor);
}

STATUS EgaDrawBitmap(LPVOID lpvBitmap, WORD wX, WORD wY){
    RECT Rect = {
        .left = 0,
        .top = 0,
        .right = ((PBITMAP)lpvBitmap)->Info.biWidth,
        .bottom = ((PBITMAP)lpvBitmap)->Info.biHeight
    };
    return EgaDrawBitmapLimited(lpvBitmap, wX, wY, &Rect);
}

STATUS EgaDrawBitmapAlpha(LPVOID lpvBitmap, WORD wX, WORD wY, BYTE bAlpha){
    RECT Rect = {
        .left = 0,
        .top = 0,
        .right = ((PBITMAP)lpvBitmap)->Info.biWidth,
        .bottom = ((PBITMAP)lpvBitmap)->Info.biHeight
    };
    return EgaDrawBitmapAlphaLimited(lpvBitmap, wX, wY, bAlpha, &Rect);
}

STATUS EgaDrawBitmapLimited(LPVOID lpvBitmap, WORD wX, WORD wY, LPRECT lpRect){
    PBITMAPFILEHEADER pHdr;
    PBITMAPINFO pInfo;
    pHdr = lpvBitmap;
    if(pHdr->bfType != 0x4D42)
        return STATUS_INVAL;
    pInfo = (PBITMAPINFO)((DWORD_PTR)lpvBitmap + sizeof(BITMAPFILEHEADER));
    if(pInfo->biSize != sizeof(BITMAPINFO))
        return STATUS_INVAL;
    if(pInfo->biPlanes != 1)
        return STATUS_INVAL;
    if(pInfo->biCompression != 0)
        return STATUS_INVAL;
    EgaLimitRect(lpRect, 320, 200);
    EgaLimitRect(lpRect, pInfo->biWidth, pInfo->biHeight);
    switch(pInfo->biBitCount){
    case 16:{
        DWORD_PTR pRow = (DWORD_PTR)lpvBitmap + pHdr->bfOffBits;
        for(REG LONG i = lpRect->top; i < lpRect->bottom; i++){
            REG LPWORD lpwBits = (LPWORD)(pRow + (lpRect->left << 1));
            for(REG LONG j = lpRect->left; j < lpRect->right; j++, lpwBits++){
                VECTOR2I vPos = {wX + j, wY + pInfo->biHeight - i - 1};
                REG BYTE bColor;
                bColor = ((*lpwBits & 0x7000) >> 7) | ((*lpwBits & 0x0300) >> 5) | ((*lpwBits & 0x001C) >> 2);
                EgaDrawPoint(&vPos, bColor);
            }
            pRow += EgaGetBitmapRowSize(pInfo->biWidth, pInfo->biBitCount);
        }
    }
        break;
    case 24:{
        DWORD_PTR pRow = (DWORD_PTR)lpvBitmap + pHdr->bfOffBits;
        for(REG LONG i = lpRect->top; i < lpRect->bottom; i++){
            DWORD_PTR pBits = pRow + lpRect->left * 3;
            for(REG LONG j = lpRect->left; j < lpRect->right; j++, pBits += 3){
                VECTOR2I vPos = {wX + j, wY + pInfo->biHeight - i - 1};
                REG BYTE bColor;
                bColor = ((*(PBYTE)pBits & 0xE0) >> 5) | ((*(PBYTE)(pBits + 1) & 0xC0) >> 3) | (*(PBYTE)(pBits + 2) & 0xE0);
                EgaDrawPoint(&vPos, bColor);
            }
            pRow += EgaGetBitmapRowSize(pInfo->biWidth, pInfo->biBitCount);
        }
    }
        break;
    default:
        return STATUS_INVAL;
    }
    return STATUS_NOERR;
}

STATUS EgaDrawBitmapAlphaLimited(LPVOID lpvBitmap, WORD wX, WORD wY, BYTE bAlpha, LPRECT lpRect){
    PBITMAPFILEHEADER pHdr;
    PBITMAPINFO pInfo;
    pHdr = lpvBitmap;
    if(pHdr->bfType != 0x4D42)
        return STATUS_INVAL;
    pInfo = (PBITMAPINFO)((DWORD_PTR)lpvBitmap + sizeof(BITMAPFILEHEADER));
    if(pInfo->biSize != sizeof(BITMAPINFO))
        return STATUS_INVAL;
    if(pInfo->biPlanes != 1)
        return STATUS_INVAL;
    if(pInfo->biCompression != 0)
        return STATUS_INVAL;
    EgaLimitRect(lpRect, 320, 200);
    EgaLimitRect(lpRect, pInfo->biWidth, pInfo->biHeight);
    switch(pInfo->biBitCount){
    case 16:{
        DWORD_PTR pRow = (DWORD_PTR)lpvBitmap + pHdr->bfOffBits;
        for(REG LONG i = lpRect->top; i < lpRect->bottom; i++){
            REG LPWORD lpwBits = (LPWORD)(pRow + (lpRect->left << 1));
            for(REG LONG j = lpRect->left; j < lpRect->right; j++, lpwBits++){
                VECTOR2I vPos = {wX + j, wY + pInfo->biHeight - i - 1};
                REG BYTE bColor;
                bColor = ((*lpwBits & 0x7000) >> 7) | ((*lpwBits & 0x0300) >> 5) | ((*lpwBits & 0x001C) >> 2);
                if(bColor == bAlpha)
                    continue;
                EgaDrawPoint(&vPos, bColor);
            }
            pRow += EgaGetBitmapRowSize(pInfo->biWidth, pInfo->biBitCount);
        }
    }
        break;
    case 24:{
        DWORD_PTR pRow = (DWORD_PTR)lpvBitmap + pHdr->bfOffBits;
        for(REG LONG i = lpRect->top; i < lpRect->bottom; i++){
            DWORD_PTR pBits = pRow + lpRect->left * 3;
            for(REG LONG j = lpRect->left; j < lpRect->right; j++, pBits += 3){
                VECTOR2I vPos = {wX + j, wY + pInfo->biHeight - i - 1};
                REG BYTE bColor;
                bColor = ((*(PBYTE)pBits & 0xE0) >> 5) | ((*(PBYTE)(pBits + 1) & 0xC0) >> 3) | (*(PBYTE)(pBits + 2) & 0xE0);
                if(bColor == bAlpha)
                    continue;
                EgaDrawPoint(&vPos, bColor);
            }
            pRow += EgaGetBitmapRowSize(pInfo->biWidth, pInfo->biBitCount);
        }
    }
        break;
    default:
        return STATUS_INVAL;
    }
    return STATUS_NOERR;
}

VOID EgaSetPalettes(BYTE bStart, BYTE bLen, PRGBQUAD pColors){
    REG WORD wLen;
    wLen = (bLen == 0 ? 0x100 : bLen);
    __outbyte(0x3C8, bStart);
    for(REG WORD i = 0; i < wLen; i++){
        __outbyte(0x3C9, pColors[i].rgbRed);
        __outbyte(0x3C9, pColors[i].rgbGreen);
        __outbyte(0x3C9, pColors[i].rgbBlue);
    }
}

VOID EgaDrawRect(LPRECT lpRect, BYTE bColor){
    EgaLimitRect(lpRect, 320, 200);
    for(REG LONG i = lpRect->top; i < lpRect->bottom; i++)
        RtlFillMemory((LPBYTE)0xC00A0000 + lpRect->left + i * 0x140, bColor, lpRect->right - lpRect->left);
}

VOID EgaDrawBorder(LPRECT lpRect, LPRECT lpPadding, BYTE bColor){
    EgaLimitRect(lpRect, 320, 200);
    for(REG LONG i = lpRect->top; i < lpRect->top + lpPadding->top; i++)
        RtlFillMemory((LPBYTE)0xC00A0000 + lpRect->left + i * 0x140, bColor, lpRect->right - lpRect->left);
    for(REG LONG i = lpRect->top + lpPadding->top; i < lpRect->bottom - lpPadding->bottom; i++){
        RtlFillMemory((LPBYTE)0xC00A0000 + lpRect->left + i * 0x140, bColor, lpPadding->left);
        RtlFillMemory((LPBYTE)0xC00A0000 + lpRect->right - lpPadding->right + i * 0x140, bColor, lpPadding->right);
    }
    for(REG LONG i = lpRect->bottom - lpPadding->bottom; i < lpRect->bottom; i++)
        RtlFillMemory((LPBYTE)0xC00A0000 + lpRect->left + i * 0x140, bColor, lpRect->right - lpRect->left);
}
